#include "memory/cache.h"
#include "burst.h"
#include <stdlib.h>
#include <time.h>
#include <cpu/reg.h>

void dram_write(hwaddr_t addr, size_t len, uint32_t data);
void cache_ddr3_read(hwaddr_t addr, void* data);
void cache_ddr3_write(hwaddr_t addr, void* data, uint8_t *mask);

void init_cache(){
    l1_t = 0;
    l2_t = 0;
    int i = 0, j = 0;
    srand(clock());
    for(;i < l1_sets;i++){
        for(;j < l1_ways;j++){
            l1_cache[i][j].valid = false;
        }
    }
    
    for(i = 0;i < l2_ways;i++){
        for(j = 0;j < l2_ways;j++){
            l2_cache[i][j].valid = l2_cache[i][j].dirty = false;
        }
    }
}

void p_cache_t(){
    printf("(l1: %lu)\t(l2: %lu)\n",l1_t, l2_t);
}

int32_t l1_read(hwaddr_t addr){
    int32_t tag_l1 = (addr >> (l1_sets_bit + block_size_bit));
    int32_t set_l1 = (addr >> (block_size_bit) & (l1_sets - 1));
    int i = 0;
    for(;i < l1_ways;i++){
        if(!l1_cache[set_l1][i].valid)continue;
        if(l1_cache[set_l1][i].tag == tag_l1){
            l1_t += 2;
            return i;
        }
    }

    for(i = 0;i < l1_ways;i++){
        if(!l1_cache[set_l1][i].valid)
            break;
    }
        if(i == l1_ways)
            i = rand() % l1_ways;

    l1_cache[set_l1][i].valid = true;
    l1_cache[set_l1][i].tag = tag_l1;
    int set_l2 = (addr >> block_size_bit) & (l2_sets - 1);
    int ii = l2_read(addr);
    memcpy(l1_cache[set_l1][i].block, l2_cache[set_l2][ii].block, block_size);
    l1_t += 200;
    return i;

    // int j = 0;
    // for(;j < block_size / BURST_LEN;j++)
    // cache_ddr3_read(((addr >> block_size_bit) << block_size_bit) + BURST_LEN * j, l1_cache[set_l1][i].block + BURST_LEN * j);
    // l1_t += 200;
    // return i;
}

void l1_write(hwaddr_t addr, size_t len, uint32_t data){
    int32_t tag_l1 = (addr >> (l1_sets_bit + block_size_bit));
    int32_t set_l1 = (addr >> block_size_bit) & (l1_sets - 1);
    int32_t imm_l1 = (addr & (block_size - 1));
    bool hit = false;
    int i = 0;
    for(;i < l1_ways;i++){
        if(!l1_cache[set_l1][i].valid)
            continue;
        if(l1_cache[set_l1][i].tag == tag_l1){
            hit = true; break;
        }
    }
    if(hit){
        if(imm_l1 + len <= block_size){
            memcpy(l1_cache[set_l1][i].block + imm_l1, &data, len);
        }
        else {
            memcpy(l1_cache[set_l1][i].block + imm_l1, &data, block_size - imm_l1);
            l1_write(addr + block_size - imm_l1, len - (block_size - imm_l1), data >> 8 * (block_size - imm_l1));
        }
        l1_t += 2;
    }
    else l1_t += 200;
    
    l2_write(addr, len, data);
}

int32_t l2_read(hwaddr_t addr){
    int32_t tag_l2 = (addr >> (l2_sets_bit + block_size_bit));
    int32_t set_l2 = (addr >> block_size_bit) & (l2_sets - 1);
    int i = 0;
    for(;i < l2_ways;i++){
        if(!l2_cache[set_l2][i].valid)
            continue;
        if(l2_cache[set_l2][i].tag == tag_l2){
            l2_t += 2;
            return i;
        }
    }
    for(i = 0;i < l2_ways;i++){
        if(!l2_cache[set_l2][i].valid)
            break;
    }

    if(i == l2_ways) i = rand() % l2_ways;

    if(l2_cache[set_l2][i].valid && l2_cache[set_l2][i].dirty){
        uint8_t temp[BURST_LEN * 2];
        memset(temp, 1, sizeof(temp));
        int j = 0;
        uint32_t addr_pre = ((l2_cache[set_l2][i].tag << (l2_sets_bit + block_size_bit)) | (set_l2 << block_size_bit));
        for(;j < block_size / BURST_LEN;j++){
            cache_ddr3_write(addr_pre + BURST_LEN * j, l2_cache[set_l2][i].block + BURST_LEN * j, temp);
        }
    }

    l2_cache[set_l2][i].valid = true;
    l2_cache[set_l2][i].tag = tag_l2;
    l2_cache[set_l2][i].dirty = false;

    int j = 0;
    for(;j < block_size / BURST_LEN;j++){
        cache_ddr3_read(((addr >> block_size_bit) << block_size_bit) + BURST_LEN * j, l2_cache[set_l2][i].block + BURST_LEN * j);
    }
    l2_t += 200;
    return i;
}

void l2_write(hwaddr_t addr, size_t len, uint32_t data){
    int32_t tag_l2 = (addr >> (l2_sets_bit + block_size_bit));
    int32_t set_l2 = (addr >> block_size_bit) & (l2_sets - 1);
    int32_t imm_l2 = (addr & (block_size - 1));
    bool hit = false;
    int i= 0;
    for(;i < l2_ways;i++){
        if(!l2_cache[set_l2][i].valid)
            continue;
        if(l2_cache[set_l2][i].tag == tag_l2){
            hit = true;
            break;
        }
    }

    if(hit){
        l2_t += 2;
        l2_cache[set_l2][i].dirty = true;
        if(imm_l2 + len <= block_size){
            memcpy(l2_cache[set_l2][i].block + imm_l2, &data, len);
        }
        else{
            memcpy(l2_cache[set_l2][i].block + imm_l2, &data, block_size - imm_l2);
            l2_write(addr + block_size - imm_l2, len - (block_size - imm_l2), data >> 8 * (block_size - imm_l2));
        }
    }
    else {
        i = l2_read(addr);
        l2_write(addr, len, data);
    }
}